1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.graphics.g2d.textrenderer; 12 import hip.graphics.mesh; 13 import hip.math.matrix; 14 import hip.api.data.font; 15 import hip.hiprenderer; 16 import hip.assetmanager; 17 public import hip.graphics.orthocamera; 18 public import hip.api.graphics.batch; 19 public import hip.api.graphics.text : HipTextAlign, Size; 20 21 /** 22 * Don't change those names. If the variable names are changed, the shaders should stop working 23 */ 24 @HipShaderInputLayout struct HipTextRendererVertex 25 { 26 import hip.math.vector; 27 Vector3 vPosition; 28 Vector2 vTexST; 29 30 this(Vector3 vPosition, Vector2 vTexST) 31 { 32 this.vPosition = vPosition; 33 this.vTexST = vTexST; 34 } 35 36 static enum size_t floatsCount = (HipTextRendererVertex.sizeof / float.sizeof); 37 static enum size_t quadsCount = floatsCount*4; 38 } 39 40 @HipShaderVertexUniform("Cbuf") 41 struct HipTextRendererVertexUniforms 42 { 43 Matrix4 uMVP = Matrix4.identity; 44 } 45 46 @HipShaderFragmentUniform("FragVars") 47 struct HipTextRendererFragmentUniforms 48 { 49 float[4] uColor = [1,1,1,1]; 50 } 51 52 53 private __gshared Shader bmTextShader = null; 54 55 /** 56 * This class oculd be refactored in the future to actually 57 * use a spritebatch for its drawing. 58 */ 59 class HipTextRenderer : IHipBatch 60 { 61 IHipFont font; 62 Mesh mesh; 63 index_t[] indices; 64 HipTextRendererVertex[] vertices; 65 66 protected HipColor color; 67 protected HipOrthoCamera camera; 68 protected float managedDepth = 0; 69 private uint quadsCount; 70 private uint lastDrawQuadsCount; 71 bool shouldRenderLineBreak, shouldRenderSpace; 72 private __gshared uint[] linesWidths; 73 74 this(HipOrthoCamera camera, index_t maxQuads = DefaultMaxSpritesPerBatch) 75 { 76 import hip.error.handler; 77 import hip.util.conv:to; 78 if(bmTextShader is null) 79 { 80 import hip.hiprenderer.initializer; 81 bmTextShader = newShader(HipShaderPresets.BITMAP_TEXT); 82 bmTextShader.addVarLayout(ShaderVariablesLayout.from!(HipTextRendererVertexUniforms)(HipRenderer.getInfo)); 83 bmTextShader.addVarLayout(ShaderVariablesLayout.from!(HipTextRendererFragmentUniforms)(HipRenderer.getInfo)); 84 bmTextShader.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD); 85 const Viewport v = HipRenderer.getCurrentViewport(); 86 bmTextShader.uMVP = Matrix4.orthoLH(0, v.width, v.height, 0, 0.01, 100); 87 bmTextShader.setDefaultBlock("FragVars"); 88 bmTextShader.sendVars(); 89 } 90 ErrorHandler.assertLazyExit(index_t.max > maxQuads * 6, "Invalid max quads. Max is "~to!string(index_t.max/6)); 91 mesh = new Mesh(HipVertexArrayObject.getVAO!HipTextRendererVertex, bmTextShader); 92 //6 indices per quad 93 vertices = new HipTextRendererVertex[](maxQuads*4); 94 mesh.setIndices(HipRenderer.getQuadIndexBuffer(maxQuads)); 95 mesh.createVertexBuffer(cast(index_t)vertices.length, HipResourceUsage.Dynamic); 96 mesh.sendAttributes(); 97 mesh.setVertices(vertices); 98 if(camera is null) 99 camera = new HipOrthoCamera(); 100 this.camera = camera; 101 102 import hip.global.gamedef; 103 //Promise it won't modify 104 setFont(cast(IHipFont)HipDefaultAssets.font); 105 } 106 107 void setCurrentDepth(float depth){managedDepth = depth;} 108 109 void setFont(IHipFont font) 110 { 111 if(this.font !is null && font !is this.font) 112 { 113 draw(); 114 } 115 this.font = font; 116 } 117 118 void setColor(HipColor color) 119 { 120 if(this.color != color) 121 { 122 if(this.color != HipColor.no) 123 draw(); 124 bmTextShader.uColor = HipColorf(color); 125 } 126 this.color = color; 127 } 128 129 /** 130 * Implementation for unchanging text. 131 * The text will be saved, represented as an internal ID to a managed static HipText. Which means the texture will be baked 132 * so it is possible to actually draw it a lot faster as all the preprocessings are done once. 133 */ 134 void draw(string str, int x, int y, float scale = 1, HipTextAlign align_ = HipTextAlign.centerLeft, Size bounds = Size.init, bool wordWrap = false) 135 { 136 import hip.api.graphics.text; 137 int vI = quadsCount*4; //vertex buffer index 138 if(vI + str.length * 4 > vertices.length) 139 { 140 flush; 141 vI = 0; 142 } 143 144 vI+= putTextVertices(font, (cast(HipTextRendererVertexAPI[])vertices)[vI..$], str, x, y, managedDepth, scale, align_, bounds, wordWrap, shouldRenderSpace); 145 quadsCount = vI/4; 146 } 147 148 ///This way it will reallocate once. 149 void addVertices(void[] vertices, IHipFont font) 150 { 151 if(vertices.length > 0) 152 { 153 setFont(font); 154 HipTextRendererVertex[] theVerts = cast(HipTextRendererVertex[])vertices; 155 if(theVerts.length+quadsCount*4 > this.vertices.length) 156 this.vertices.length = theVerts.length+quadsCount*4; 157 this.vertices[quadsCount*4..quadsCount*4+theVerts.length] = theVerts[0..$]; 158 quadsCount+= theVerts.length/4; 159 } 160 } 161 162 void draw() 163 { 164 if(font is null) 165 { 166 import hip.error.handler; 167 ErrorHandler.showWarningMessage("Font Missing", "No font attached on HipTextRenderer"); 168 return; 169 } 170 171 if(quadsCount - lastDrawQuadsCount != 0) 172 { 173 mesh.bind(); 174 this.font.texture.bind(); 175 mesh.shader.setVertexVar("Cbuf.uMVP", camera.getMVP(), true); 176 mesh.shader.sendVars(); 177 178 size_t start = lastDrawQuadsCount*4; 179 size_t end = quadsCount*4; 180 181 mesh.updateVertices(vertices[start..end], cast(int)start); 182 183 mesh.draw((quadsCount - lastDrawQuadsCount)*6, HipRendererMode.triangles, lastDrawQuadsCount*6); 184 font.texture.unbind(); 185 mesh.unbind(); 186 } 187 lastDrawQuadsCount = quadsCount; 188 } 189 /** 190 * Flush should be took care since it could make it rewrite to the same part of the buffer agin. 191 * While shadow buffering is not implemented, use it as that. 192 */ 193 void flush() 194 { 195 draw(); 196 lastDrawQuadsCount = quadsCount = 0; 197 } 198 }